## This file is part of Scapy
## See http://www.secdev.org/projects/scapy for more informations
## Copyright (C) Philippe Biondi <phil@secdev.org>
## This program is published under a GPLv2 license

## Copyright (C) 2005  Guillaume Valadon <guedou@hongo.wide.ad.jp>
##                     Arnaud Ebalard <arnaud.ebalard@eads.net>

"""
DHCPv6: Dynamic Host Configuration Protocol for IPv6. [RFC 3315]
"""

import socket
from scapy.packet import *
from scapy.fields import *
from scapy.utils6 import *
from scapy.layers.inet6 import *
from scapy.ansmachine import AnsweringMachine

#############################################################################
# Helpers                                                                  ##
#############################################################################

def get_cls(name, fallback_cls):
    return globals().get(name, fallback_cls)


#############################################################################
#############################################################################
###                                DHCPv6                                 ###
#############################################################################
#############################################################################

All_DHCP_Relay_Agents_and_Servers = "ff02::1:2" 
All_DHCP_Servers = "ff05::1:3"  # Site-Local scope : deprecated by 3879

dhcp6opts = { 1: "CLIENTID",  
              2: "SERVERID",
              3: "IA_NA",
              4: "IA_TA",
              5: "IAADDR",
              6: "ORO",
              7: "PREFERENCE",
              8: "ELAPSED_TIME",
              9: "RELAY_MSG",
             11: "AUTH",
             12: "UNICAST",
             13: "STATUS_CODE",
             14: "RAPID_COMMIT",
             15: "USER_CLASS",
             16: "VENDOR_CLASS",
             17: "VENDOR_OPTS",
             18: "INTERFACE_ID",
             19: "RECONF_MSG",
             20: "RECONF_ACCEPT",
             21: "SIP Servers Domain Name List",     #RFC3319
             22: "SIP Servers IPv6 Address List",    #RFC3319
             23: "DNS Recursive Name Server Option", #RFC3646
             24: "Domain Search List option",        #RFC3646
             25: "OPTION_IA_PD",                     #RFC3633
             26: "OPTION_IAPREFIX",                  #RFC3633
             27: "OPTION_NIS_SERVERS",               #RFC3898
             28: "OPTION_NISP_SERVERS",              #RFC3898
             29: "OPTION_NIS_DOMAIN_NAME",           #RFC3898
             30: "OPTION_NISP_DOMAIN_NAME",          #RFC3898
             31: "OPTION_SNTP_SERVERS",              #RFC4075
             32: "OPTION_INFORMATION_REFRESH_TIME",  #RFC4242
             33: "OPTION_BCMCS_SERVER_D",            #RFC4280         
             34: "OPTION_BCMCS_SERVER_A",            #RFC4280
             36: "OPTION_GEOCONF_CIVIC",             #RFC-ietf-geopriv-dhcp-civil-09.txt
             37: "OPTION_REMOTE_ID",                 #RFC4649
             38: "OPTION_SUBSCRIBER_ID",             #RFC4580
             39: "OPTION_CLIENT_FQDN" }              #RFC4704

dhcp6opts_by_code = {  1: "DHCP6OptClientId", 
                       2: "DHCP6OptServerId",
                       3: "DHCP6OptIA_NA",
                       4: "DHCP6OptIA_TA",
                       5: "DHCP6OptIAAddress",
                       6: "DHCP6OptOptReq",
                       7: "DHCP6OptPref",
                       8: "DHCP6OptElapsedTime",
                       9: "DHCP6OptRelayMsg",
                       11: "DHCP6OptAuth",
                       12: "DHCP6OptServerUnicast",
                       13: "DHCP6OptStatusCode",
                       14: "DHCP6OptRapidCommit",
                       15: "DHCP6OptUserClass",
                       16: "DHCP6OptVendorClass",
                       17: "DHCP6OptVendorSpecificInfo",
                       18: "DHCP6OptIfaceId",
                       19: "DHCP6OptReconfMsg",
                       20: "DHCP6OptReconfAccept",
                       21: "DHCP6OptSIPDomains",          #RFC3319
                       22: "DHCP6OptSIPServers",          #RFC3319
                       23: "DHCP6OptDNSServers",          #RFC3646
                       24: "DHCP6OptDNSDomains",          #RFC3646
                       25: "DHCP6OptIA_PD",               #RFC3633
                       26: "DHCP6OptIAPrefix",            #RFC3633
                       27: "DHCP6OptNISServers",          #RFC3898
                       28: "DHCP6OptNISPServers",         #RFC3898
                       29: "DHCP6OptNISDomain",           #RFC3898
                       30: "DHCP6OptNISPDomain",          #RFC3898
                       31: "DHCP6OptSNTPServers",         #RFC4075
                       32: "DHCP6OptInfoRefreshTime",     #RFC4242
                       33: "DHCP6OptBCMCSDomains",        #RFC4280         
                       34: "DHCP6OptBCMCSServers",        #RFC4280
                       #36: "DHCP6OptGeoConf",            #RFC-ietf-geopriv-dhcp-civil-09.txt
                       37: "DHCP6OptRemoteID",            #RFC4649
                       38: "DHCP6OptSubscriberID",        #RFC4580
                       39: "DHCP6OptClientFQDN",          #RFC4704
                       #40: "DHCP6OptPANAAgent",          #RFC-ietf-dhc-paa-option-05.txt
                       #41: "DHCP6OptNewPOSIXTimeZone,    #RFC4833
                       #42: "DHCP6OptNewTZDBTimeZone,     #RFC4833
                       43: "DHCP6OptRelayAgentERO"        #RFC4994
                       #44: "DHCP6OptLQQuery",            #RFC5007
                       #45: "DHCP6OptLQClientData",       #RFC5007
                       #46: "DHCP6OptLQClientTime",       #RFC5007
                       #47: "DHCP6OptLQRelayData",        #RFC5007
                       #48: "DHCP6OptLQClientLink",       #RFC5007
}


# sect 5.3 RFC 3315 : DHCP6 Messages types
dhcp6types = {   1:"SOLICIT",
                 2:"ADVERTISE",
                 3:"REQUEST",
                 4:"CONFIRM",
                 5:"RENEW",
                 6:"REBIND",
                 7:"REPLY",
                 8:"RELEASE",
                 9:"DECLINE",
                10:"RECONFIGURE",
                11:"INFORMATION-REQUEST",
                12:"RELAY-FORW",
                13:"RELAY-REPL" }


#####################################################################
###                  DHCPv6 DUID related stuff                    ###
#####################################################################

duidtypes = { 1: "Link-layer address plus time", 
              2: "Vendor-assigned unique ID based on Enterprise Number",
              3: "Link-layer Address" }

# DUID hardware types - RFC 826 - Extracted from 
# http://www.iana.org/assignments/arp-parameters on 31/10/06
# We should add the length of every kind of address.
duidhwtypes = {  0: "NET/ROM pseudo", # Not referenced by IANA
                 1: "Ethernet (10Mb)",
                 2: "Experimental Ethernet (3Mb)",
                 3: "Amateur Radio AX.25",
                 4: "Proteon ProNET Token Ring",
                 5: "Chaos",
                 6: "IEEE 802 Networks",
                 7: "ARCNET",
                 8: "Hyperchannel",
                 9: "Lanstar",
                10: "Autonet Short Address",
                11: "LocalTalk",
                12: "LocalNet (IBM PCNet or SYTEK LocalNET)",
                13: "Ultra link",
                14: "SMDS",
                15: "Frame Relay",
                16: "Asynchronous Transmission Mode (ATM)",
                17: "HDLC",
                18: "Fibre Channel",
                19: "Asynchronous Transmission Mode (ATM)",
                20: "Serial Line",
                21: "Asynchronous Transmission Mode (ATM)",
                22: "MIL-STD-188-220",
                23: "Metricom",
                24: "IEEE 1394.1995",
                25: "MAPOS",
                26: "Twinaxial",
                27: "EUI-64",
                28: "HIPARP",
                29: "IP and ARP over ISO 7816-3",
                30: "ARPSec",
                31: "IPsec tunnel",
                32: "InfiniBand (TM)",
                33: "TIA-102 Project 25 Common Air Interface (CAI)" }

class UTCTimeField(IntField):
    epoch = (2000, 1, 1, 0, 0, 0, 5, 1, 0) # required Epoch
    def i2repr(self, pkt, x):
        x = self.i2h(pkt, x)
        from time import gmtime, strftime, mktime
        delta = mktime(self.epoch) - mktime(gmtime(0))
        x = x + delta
        t = strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime(x))
        return "%s (%d)" % (t, x)

class _LLAddrField(MACField):
    pass

# XXX We only support Ethernet addresses at the moment. _LLAddrField 
#     will be modified when needed. Ask us. --arno
class DUID_LLT(Packet):  # sect 9.2 RFC 3315
    name = "DUID - Link-layer address plus time"
    fields_desc = [ ShortEnumField("type", 1, duidtypes),
                    XShortEnumField("hwtype", 1, duidhwtypes), 
                    UTCTimeField("timeval", 0), # i.e. 01 Jan 2000
                    _LLAddrField("lladdr", ETHER_ANY) ]

# In fact, IANA enterprise-numbers file available at 
# http//www.iana.org/asignments/enterprise-numbers)
# is simply huge (more than 2Mo and 600Ko in bz2). I'll
# add only most common vendors, and encountered values.
# -- arno
iana_enterprise_num = {    9: "ciscoSystems",
                          35: "Nortel Networks",
                          43: "3Com",
                         311: "Microsoft",
                        2636: "Juniper Networks, Inc.",
                        4526: "Netgear",
                        5771: "Cisco Systems, Inc.",
                        5842: "Cisco Systems",
                       16885: "Nortel Networks" }

class DUID_EN(Packet):  # sect 9.3 RFC 3315
    name = "DUID - Assigned by Vendor Based on Enterprise Number"
    fields_desc = [ ShortEnumField("type", 2, duidtypes),
                    IntEnumField("enterprisenum", 311, iana_enterprise_num),
                    StrField("id","") ] 

class DUID_LL(Packet):  # sect 9.4 RFC 3315
    name = "DUID - Based on Link-layer Address"
    fields_desc = [ ShortEnumField("type", 3, duidtypes),
                    XShortEnumField("hwtype", 1, duidhwtypes), 
                    _LLAddrField("lladdr", ETHER_ANY) ]

duid_cls = { 1: "DUID_LLT",
             2: "DUID_EN",
             3: "DUID_LL"}

#####################################################################
###                   DHCPv6 Options classes                      ###
#####################################################################

class _DHCP6OptGuessPayload(Packet):
    def guess_payload_class(self, payload):
        cls = conf.raw_layer
        if len(payload) > 2 :
            opt = struct.unpack("!H", payload[:2])[0]
            cls = get_cls(dhcp6opts_by_code.get(opt, "DHCP6OptUnknown"), DHCP6OptUnknown)
        return cls

class DHCP6OptUnknown(_DHCP6OptGuessPayload): # A generic DHCPv6 Option
    name = "Unknown DHCPv6 OPtion"
    fields_desc = [ ShortEnumField("optcode", 0, dhcp6opts), 
                    FieldLenField("optlen", None, length_of="data", fmt="!H"),
                    StrLenField("data", "",
                                length_from = lambda pkt: pkt.optlen)]

class _DUIDField(PacketField):
    holds_packets=1
    def __init__(self, name, default, length_from=None):
        StrField.__init__(self, name, default)
        self.length_from = length_from

    def i2m(self, pkt, i):
        return str(i)

    def m2i(self, pkt, x):
        cls = conf.raw_layer
        if len(x) > 4:
            o = struct.unpack("!H", x[:2])[0]
            cls = get_cls(duid_cls.get(o, conf.raw_layer), conf.raw_layer)
        return cls(x)

    def getfield(self, pkt, s):
        l = self.length_from(pkt)
        return s[l:], self.m2i(pkt,s[:l])
 

class DHCP6OptClientId(_DHCP6OptGuessPayload):     # RFC sect 22.2
    name = "DHCP6 Client Identifier Option"
    fields_desc = [ ShortEnumField("optcode", 1, dhcp6opts), 
                    FieldLenField("optlen", None, length_of="duid", fmt="!H"),
                    _DUIDField("duid", "",
                               length_from = lambda pkt: pkt.optlen) ]


class DHCP6OptServerId(DHCP6OptClientId):     # RFC sect 22.3
    name = "DHCP6 Server Identifier Option"
    optcode = 2

# Should be encapsulated in the option field of IA_NA or IA_TA options
# Can only appear at that location.
# TODO : last field IAaddr-options is not defined in the reference document
class DHCP6OptIAAddress(_DHCP6OptGuessPayload):    # RFC sect 22.6
    name = "DHCP6 IA Address Option (IA_TA or IA_NA suboption)"
    fields_desc = [ ShortEnumField("optcode", 5, dhcp6opts), 
                    FieldLenField("optlen", None, length_of="iaaddropts",
                                  fmt="!H", adjust = lambda pkt,x: x+24),
                    IP6Field("addr", "::"),
                    IntField("preflft", 0),
                    IntField("validlft", 0),
                    XIntField("iaid", None),
                    StrLenField("iaaddropts", "",
                                length_from  = lambda pkt: pkt.optlen - 24) ]
    def guess_payload_class(self, payload):
        return conf.padding_layer

class _IANAOptField(PacketListField):
    def i2len(self, pkt, z):
        if z is None or z == []:
            return 0
        return sum(map(lambda x: len(str(x)) ,z))

    def getfield(self, pkt, s):
        l = self.length_from(pkt)
        lst = []
        remain, payl = s[:l], s[l:]
        while len(remain)>0:
            p = self.m2i(pkt,remain)
            if conf.padding_layer in p:
                pad = p[conf.padding_layer]
                remain = pad.load
                del(pad.underlayer.payload)
            else:
                remain = ""
            lst.append(p)
        return payl,lst

class DHCP6OptIA_NA(_DHCP6OptGuessPayload):         # RFC sect 22.4
    name = "DHCP6 Identity Association for Non-temporary Addresses Option"
    fields_desc = [ ShortEnumField("optcode", 3, dhcp6opts), 
                    FieldLenField("optlen", None, length_of="ianaopts",
                                  fmt="!H", adjust = lambda pkt,x: x+12),
                    XIntField("iaid", None),
                    IntField("T1", None),
                    IntField("T2", None),
                    _IANAOptField("ianaopts", [], DHCP6OptIAAddress,
                                  length_from = lambda pkt: pkt.optlen-12) ]

class _IATAOptField(_IANAOptField):
    pass

class DHCP6OptIA_TA(_DHCP6OptGuessPayload):         # RFC sect 22.5
    name = "DHCP6 Identity Association for Temporary Addresses Option"
    fields_desc = [ ShortEnumField("optcode", 4, dhcp6opts), 
                    FieldLenField("optlen", None, length_of="iataopts",
                                  fmt="!H", adjust = lambda pkt,x: x+4),
                    XIntField("iaid", None),
                    _IATAOptField("iataopts", [], DHCP6OptIAAddress,
                                  length_from = lambda pkt: pkt.optlen-4) ]


#### DHCPv6 Option Request Option ###################################

class _OptReqListField(StrLenField):
    islist = 1
    def i2h(self, pkt, x):
        if x is None:
            return []
        return x

    def i2len(self, pkt, x):
        return 2*len(x)

    def any2i(self, pkt, x):
        return x

    def i2repr(self, pkt, x):
        s = []
        for y in self.i2h(pkt, x):
            if dhcp6opts.has_key(y):
                s.append(dhcp6opts[y])
            else:
                s.append("%d" % y)
        return "[%s]" % ", ".join(s) 

    def m2i(self, pkt, x):
        r = []
        while len(x) != 0:
            if len(x)<2:
                warning("Odd length for requested option field. Rejecting last byte")
                return r
            r.append(struct.unpack("!H", x[:2])[0])
            x = x[2:]
        return r
    
    def i2m(self, pkt, x):
        return "".join(map(lambda y: struct.pack("!H", y), x))

# A client may include an ORO in a solicit, Request, Renew, Rebind,
# Confirm or Information-request
class DHCP6OptOptReq(_DHCP6OptGuessPayload):       # RFC sect 22.7
    name = "DHCP6 Option Request Option"
    fields_desc = [ ShortEnumField("optcode", 6, dhcp6opts),
                    FieldLenField("optlen", None, length_of="reqopts", fmt="!H"),
                    _OptReqListField("reqopts", [23, 24],
                                     length_from = lambda pkt: pkt.optlen) ]


#### DHCPv6 Preference Option #######################################

# emise par un serveur pour affecter le choix fait par le client. Dans
# les messages Advertise, a priori
class DHCP6OptPref(_DHCP6OptGuessPayload):       # RFC sect 22.8
    name = "DHCP6 Preference Option"
    fields_desc = [ ShortEnumField("optcode", 7, dhcp6opts), 
                    ShortField("optlen", 1 ),
                    ByteField("prefval",255) ]


#### DHCPv6 Elapsed Time Option #####################################

class _ElapsedTimeField(ShortField):
    def i2repr(self, pkt, x):
        if x == 0xffff:
            return "infinity (0xffff)"
        return "%.2f sec" % (self.i2h(pkt, x)/100.)

class DHCP6OptElapsedTime(_DHCP6OptGuessPayload):# RFC sect 22.9
    name = "DHCP6 Elapsed Time Option"
    fields_desc = [ ShortEnumField("optcode", 8, dhcp6opts), 
                    ShortField("optlen", 2),
                    _ElapsedTimeField("elapsedtime", 0) ]


#### DHCPv6 Relay Message Option ####################################

# Relayed message is seen as a payload.
class DHCP6OptRelayMsg(_DHCP6OptGuessPayload):# RFC sect 22.10
    name = "DHCP6 Relay Message Option"
    fields_desc = [ ShortEnumField("optcode", 9, dhcp6opts), 
                    ShortField("optlen", None ) ]
    def post_build(self, p, pay):
        if self.optlen is None:
            l = len(pay) 
            p = p[:2]+struct.pack("!H", l)
        return p + pay


#### DHCPv6 Authentication Option ###################################

#    The following fields are set in an Authentication option for the
#    Reconfigure Key Authentication Protocol:
#
#       protocol    3
#
#       algorithm   1
#
#       RDM         0
#
#    The format of the Authentication information for the Reconfigure Key
#    Authentication Protocol is:
#
#      0                   1                   2                   3
#      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
#     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
#     |     Type      |                 Value (128 bits)              |
#     +-+-+-+-+-+-+-+-+                                               |
#     .                                                               .
#     .                                                               .
#     .                                               +-+-+-+-+-+-+-+-+
#     |                                               |
#     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
#
#       Type    Type of data in Value field carried in this option:
#
#                  1   Reconfigure Key value (used in Reply message).
#
#                  2   HMAC-MD5 digest of the message (used in Reconfigure
#                      message).
#
#       Value   Data as defined by field.


# TODO : Decoding only at the moment
class DHCP6OptAuth(_DHCP6OptGuessPayload):    # RFC sect 22.11
    name = "DHCP6 Option - Authentication"
    fields_desc = [ ShortEnumField("optcode", 11, dhcp6opts), 
                    FieldLenField("optlen", None, length_of="authinfo",
                                  adjust = lambda pkt,x: x+11),
                    ByteField("proto", 3), # TODO : XXX
                    ByteField("alg", 1), # TODO : XXX
                    ByteField("rdm", 0), # TODO : XXX
                    StrFixedLenField("replay", "A"*8, 8), # TODO: XXX
                    StrLenField("authinfo", "",
                                length_from = lambda pkt: pkt.optlen - 11) ]

#### DHCPv6 Server Unicast Option ###################################

class _SrvAddrField(IP6Field):
    def i2h(self, pkt, x):
        if x is None:
            return "::"
        return x
    
    def i2m(self, pkt, x):
        return inet_pton(socket.AF_INET6, self.i2h(pkt,x))

class DHCP6OptServerUnicast(_DHCP6OptGuessPayload):# RFC sect 22.12
    name = "DHCP6 Server Unicast Option"
    fields_desc = [ ShortEnumField("optcode", 12, dhcp6opts), 
                    ShortField("optlen", 16 ),
                    _SrvAddrField("srvaddr",None) ]


#### DHCPv6 Status Code Option ######################################

dhcp6statuscodes = { 0:"Success",      # sect 24.4
                     1:"UnspecFail",
                     2:"NoAddrsAvail",
                     3:"NoBinding",
                     4:"NotOnLink",
                     5:"UseMulticast",
                     6:"NoPrefixAvail"} # From RFC3633

class DHCP6OptStatusCode(_DHCP6OptGuessPayload):# RFC sect 22.13
    name = "DHCP6 Status Code Option"
    fields_desc = [ ShortEnumField("optcode", 13, dhcp6opts), 
                    FieldLenField("optlen", None, length_of="statusmsg",
                                  fmt="!H", adjust = lambda pkt,x:x+2),
                    ShortEnumField("statuscode",None,dhcp6statuscodes),
                    StrLenField("statusmsg", "",
                                length_from = lambda pkt: pkt.optlen-2) ]


#### DHCPv6 Rapid Commit Option #####################################

class DHCP6OptRapidCommit(_DHCP6OptGuessPayload):   # RFC sect 22.14
    name = "DHCP6 Rapid Commit Option"
    fields_desc = [ ShortEnumField("optcode", 14, dhcp6opts),
                    ShortField("optlen", 0)]


#### DHCPv6 User Class Option #######################################

class _UserClassDataField(PacketListField):
    def i2len(self, pkt, z):
        if z is None or z == []:
            return 0
        return sum(map(lambda x: len(str(x)) ,z))

    def getfield(self, pkt, s):
        l = self.length_from(pkt)
        lst = []
        remain, payl = s[:l], s[l:]
        while len(remain)>0:
            p = self.m2i(pkt,remain)
            if conf.padding_layer in p:
                pad = p[conf.padding_layer]
                remain = pad.load
                del(pad.underlayer.payload)
            else:
                remain = ""
            lst.append(p)
        return payl,lst


class USER_CLASS_DATA(Packet):
    name = "user class data"
    fields_desc = [ FieldLenField("len", None, length_of="data"),
                    StrLenField("data", "",
                                length_from = lambda pkt: pkt.len) ]
    def guess_payload_class(self, payload):
        return conf.padding_layer

class DHCP6OptUserClass(_DHCP6OptGuessPayload):# RFC sect 22.15
    name = "DHCP6 User Class Option"
    fields_desc = [ ShortEnumField("optcode", 15, dhcp6opts), 
                    FieldLenField("optlen", None, fmt="!H",
                                  length_of="userclassdata"),
                    _UserClassDataField("userclassdata", [], USER_CLASS_DATA,
                                        length_from = lambda pkt: pkt.optlen) ]


#### DHCPv6 Vendor Class Option #####################################

class _VendorClassDataField(_UserClassDataField):
    pass

class VENDOR_CLASS_DATA(USER_CLASS_DATA):
    name = "vendor class data"

class DHCP6OptVendorClass(_DHCP6OptGuessPayload):# RFC sect 22.16
    name = "DHCP6 Vendor Class Option"
    fields_desc = [ ShortEnumField("optcode", 16, dhcp6opts), 
                    FieldLenField("optlen", None, length_of="vcdata", fmt="!H",
                                  adjust = lambda pkt,x: x+4),
                    IntEnumField("enterprisenum",None , iana_enterprise_num ),
                    _VendorClassDataField("vcdata", [], VENDOR_CLASS_DATA,
                                          length_from = lambda pkt: pkt.optlen-4) ]

#### DHCPv6 Vendor-Specific Information Option ######################

class VENDOR_SPECIFIC_OPTION(_DHCP6OptGuessPayload):
    name = "vendor specific option data"
    fields_desc = [ ShortField("optcode", None),
                    FieldLenField("optlen", None, length_of="optdata"),
                    StrLenField("optdata", "",
                                length_from = lambda pkt: pkt.optlen) ]
    def guess_payload_class(self, payload):
        return conf.padding_layer

# The third one that will be used for nothing interesting
class DHCP6OptVendorSpecificInfo(_DHCP6OptGuessPayload):# RFC sect 22.17
    name = "DHCP6 Vendor-specific Information Option"
    fields_desc = [ ShortEnumField("optcode", 17, dhcp6opts), 
                    FieldLenField("optlen", None, length_of="vso", fmt="!H",
                                  adjust = lambda pkt,x: x+4),
                    IntEnumField("enterprisenum",None , iana_enterprise_num),
                    _VendorClassDataField("vso", [], VENDOR_SPECIFIC_OPTION,
                                          length_from = lambda pkt: pkt.optlen-4) ]

#### DHCPv6 Interface-ID Option #####################################

# Repasser sur cette option a la fin. Elle a pas l'air d'etre des
# masses critique.
class DHCP6OptIfaceId(_DHCP6OptGuessPayload):# RFC sect 22.18
    name = "DHCP6 Interface-Id Option"
    fields_desc = [ ShortEnumField("optcode", 18, dhcp6opts),
                    FieldLenField("optlen", None, fmt="!H",
                                  length_of="ifaceid"),
                    StrLenField("ifaceid", "",
                                length_from = lambda pkt: pkt.optlen) ]


#### DHCPv6 Reconfigure Message Option ##############################

# A server includes a Reconfigure Message option in a Reconfigure
# message to indicate to the client whether the client responds with a
# renew message or an Informatiion-request message.
class DHCP6OptReconfMsg(_DHCP6OptGuessPayload):       # RFC sect 22.19
    name = "DHCP6 Reconfigure Message Option"
    fields_desc = [ ShortEnumField("optcode", 19, dhcp6opts), 
                    ShortField("optlen", 1 ),
                    ByteEnumField("msgtype", 11, {  5:"Renew Message", 
                                                   11:"Information Request"}) ]


#### DHCPv6 Reconfigure Accept Option ###############################

# A client uses the Reconfigure Accept option to announce to the
# server whether the client is willing to accept Recoonfigure
# messages, and a server uses this option to tell the client whether
# or not to accept Reconfigure messages. The default behavior in the
# absence of this option, means unwillingness to accept reconfigure
# messages, or instruction not to accept Reconfigure messages, for the
# client and server messages, respectively.
class DHCP6OptReconfAccept(_DHCP6OptGuessPayload):   # RFC sect 22.20
    name = "DHCP6 Reconfigure Accept Option"
    fields_desc = [ ShortEnumField("optcode", 20, dhcp6opts),
                    ShortField("optlen", 0)]

# As required in Sect 8. of RFC 3315, Domain Names must be encoded as 
# described in section 3.1 of RFC 1035
# XXX Label should be at most 63 octets in length : we do not enforce it
#     Total length of domain should be 255 : we do not enforce it either
class DomainNameListField(StrLenField):
    islist = 1

    def i2len(self, pkt, x):
        return len(self.i2m(pkt, x))

    def m2i(self, pkt, x):
        res = []
        while x:
            cur = []
            while x and x[0] != '\x00':
                l = ord(x[0])
                cur.append(x[1:l+1])
                x = x[l+1:]
            res.append(".".join(cur))
            if x and x[0] == '\x00':
                x = x[1:]
        return res

    def i2m(self, pkt, x):
        def conditionalTrailingDot(z):
            if z and z[-1] == '\x00':
                return z
            return z+'\x00'
        res = ""
        tmp = map(lambda y: map((lambda z: chr(len(z))+z), y.split('.')), x)
        return "".join(map(lambda x: conditionalTrailingDot("".join(x)), tmp))

class DHCP6OptSIPDomains(_DHCP6OptGuessPayload):       #RFC3319
    name = "DHCP6 Option - SIP Servers Domain Name List"
    fields_desc = [ ShortEnumField("optcode", 21, dhcp6opts),
                    FieldLenField("optlen", None, length_of="sipdomains"),
                    DomainNameListField("sipdomains", [],
                                        length_from = lambda pkt: pkt.optlen) ]

class DHCP6OptSIPServers(_DHCP6OptGuessPayload):          #RFC3319
    name = "DHCP6 Option - SIP Servers IPv6 Address List"
    fields_desc = [ ShortEnumField("optcode", 22, dhcp6opts),
                    FieldLenField("optlen", None, length_of="sipservers"),
                    IP6ListField("sipservers", [], 
                                 length_from = lambda pkt: pkt.optlen) ]

class DHCP6OptDNSServers(_DHCP6OptGuessPayload):          #RFC3646
    name = "DHCP6 Option - DNS Recursive Name Server"
    fields_desc = [ ShortEnumField("optcode", 23, dhcp6opts),
                    FieldLenField("optlen", None, length_of="dnsservers"),
                    IP6ListField("dnsservers", [],
                                 length_from = lambda pkt: pkt.optlen) ]

class DHCP6OptDNSDomains(_DHCP6OptGuessPayload): #RFC3646
    name = "DHCP6 Option - Domain Search List option"
    fields_desc = [ ShortEnumField("optcode", 24, dhcp6opts),
                    FieldLenField("optlen", None, length_of="dnsdomains"),
                    DomainNameListField("dnsdomains", [],
                                        length_from = lambda pkt: pkt.optlen) ]

# TODO: Implement iaprefopts correctly when provided with more 
#       information about it.
class DHCP6OptIAPrefix(_DHCP6OptGuessPayload):                    #RFC3633
    name = "DHCP6 Option - IA_PD Prefix option"
    fields_desc = [ ShortEnumField("optcode", 26, dhcp6opts),
                    FieldLenField("optlen", None, length_of="iaprefopts",
                                  adjust = lambda pkt,x: x+26),
                    IntField("preflft", 0),
                    IntField("validlft", 0),
                    ByteField("plen", 48),  # TODO: Challenge that default value
                    IP6Field("prefix", "2001:db8::"), # At least, global and won't hurt
                    StrLenField("iaprefopts", "",
                                length_from = lambda pkt: pkt.optlen-26) ]

class DHCP6OptIA_PD(_DHCP6OptGuessPayload):                       #RFC3633
    name = "DHCP6 Option - Identity Association for Prefix Delegation"
    fields_desc = [ ShortEnumField("optcode", 25, dhcp6opts),
                    FieldLenField("optlen", None, length_of="iapdopt",
                                  adjust = lambda pkt,x: x+12),
                    IntField("iaid", 0),
                    IntField("T1", 0),
                    IntField("T2", 0),
                    PacketListField("iapdopt", [], DHCP6OptIAPrefix,
                                    length_from = lambda pkt: pkt.optlen-12) ]

class DHCP6OptNISServers(_DHCP6OptGuessPayload):                 #RFC3898
    name = "DHCP6 Option - NIS Servers"
    fields_desc = [ ShortEnumField("optcode", 27, dhcp6opts),
                    FieldLenField("optlen", None, length_of="nisservers"),
                    IP6ListField("nisservers", [],
                                 length_from = lambda pkt: pkt.optlen) ]

class DHCP6OptNISPServers(_DHCP6OptGuessPayload):                #RFC3898
    name = "DHCP6 Option - NIS+ Servers"
    fields_desc = [ ShortEnumField("optcode", 28, dhcp6opts),
                    FieldLenField("optlen", None, length_of="nispservers"),
                    IP6ListField("nispservers", [],
                                 length_from = lambda pkt: pkt.optlen) ]

class DomainNameField(StrLenField):
    def getfield(self, pkt, s):
        l = self.length_from(pkt)
        return s[l:], self.m2i(pkt,s[:l])

    def i2len(self, pkt, x):
        return len(self.i2m(pkt, x))

    def m2i(self, pkt, x):
        save = x
        cur = []
        while x and x[0] != '\x00':
            l = ord(x[0])
            cur.append(x[1:1+l])
            x = x[l+1:]
        if x[0] != '\x00':
            print "Found weird domain: '%s'. Keeping %s" % (save, x)
        return ".".join(cur)

    def i2m(self, pkt, x):
        def conditionalTrailingDot(z):
            if (z and z[-1] == '\x00'):
                return z
            return z+'\x00'
        if not x:
            return ""
        tmp = "".join(map(lambda z: chr(len(z))+z, x.split('.')))
        return conditionalTrailingDot(tmp)

class DHCP6OptNISDomain(_DHCP6OptGuessPayload):             #RFC3898
    name = "DHCP6 Option - NIS Domain Name"
    fields_desc = [ ShortEnumField("optcode", 29, dhcp6opts),
                    FieldLenField("optlen", None, length_of="nisdomain"),
                    DomainNameField("nisdomain", "",
                                    length_from = lambda pkt: pkt.optlen) ]

class DHCP6OptNISPDomain(_DHCP6OptGuessPayload):            #RFC3898
    name = "DHCP6 Option - NIS+ Domain Name"
    fields_desc = [ ShortEnumField("optcode", 30, dhcp6opts),
                    FieldLenField("optlen", None, length_of="nispdomain"),
                    DomainNameField("nispdomain", "",
                                    length_from= lambda pkt: pkt.optlen) ]

class DHCP6OptSNTPServers(_DHCP6OptGuessPayload):                #RFC4075
    name = "DHCP6 option - SNTP Servers"
    fields_desc = [ ShortEnumField("optcode", 31, dhcp6opts),
                    FieldLenField("optlen", None, length_of="sntpservers"),
                    IP6ListField("sntpservers", [],
                                 length_from = lambda pkt: pkt.optlen) ]

IRT_DEFAULT=86400
IRT_MINIMUM=600
class DHCP6OptInfoRefreshTime(_DHCP6OptGuessPayload):    #RFC4242
    name = "DHCP6 Option - Information Refresh Time"
    fields_desc = [ ShortEnumField("optcode", 32, dhcp6opts),
                    ShortField("optlen", 4),
                    IntField("reftime", IRT_DEFAULT)] # One day

class DHCP6OptBCMCSDomains(_DHCP6OptGuessPayload):              #RFC4280         
    name = "DHCP6 Option - BCMCS Domain Name List"
    fields_desc = [ ShortEnumField("optcode", 33, dhcp6opts),
                    FieldLenField("optlen", None, length_of="bcmcsdomains"),
                    DomainNameListField("bcmcsdomains", [],
                                        length_from = lambda pkt: pkt.optlen) ]

class DHCP6OptBCMCSServers(_DHCP6OptGuessPayload):              #RFC4280
    name = "DHCP6 Option - BCMCS Addresses List"
    fields_desc = [ ShortEnumField("optcode", 34, dhcp6opts),
                    FieldLenField("optlen", None, length_of="bcmcsservers"),
                    IP6ListField("bcmcsservers", [],
                                 length_from= lambda pkt: pkt.optlen) ]

# TODO : Does Nothing at the moment
class DHCP6OptGeoConf(_DHCP6OptGuessPayload):               #RFC-ietf-geopriv-dhcp-civil-09.txt
    name = ""
    fields_desc = [ ShortEnumField("optcode", 36, dhcp6opts),
                    FieldLenField("optlen", None, length_of="optdata"),
                    StrLenField("optdata", "",
                                length_from = lambda pkt: pkt.optlen) ]

# TODO: see if we encounter opaque values from vendor devices
class DHCP6OptRemoteID(_DHCP6OptGuessPayload):                   #RFC4649
    name = "DHCP6 Option - Relay Agent Remote-ID"
    fields_desc = [ ShortEnumField("optcode", 37, dhcp6opts),
                    FieldLenField("optlen", None, length_of="remoteid",
                                  adjust = lambda pkt,x: x+4),
                    IntEnumField("enterprisenum", None, iana_enterprise_num),
                    StrLenField("remoteid", "",
                                length_from = lambda pkt: pkt.optlen-4) ]

# TODO : 'subscriberid' default value should be at least 1 byte long
class DHCP6OptSubscriberID(_DHCP6OptGuessPayload):               #RFC4580
    name = "DHCP6 Option - Subscriber ID"
    fields_desc = [ ShortEnumField("optcode", 38, dhcp6opts),
                    FieldLenField("optlen", None, length_of="subscriberid"),
                    StrLenField("subscriberid", "",
                                length_from = lambda pkt: pkt.optlen) ]

# TODO :  "The data in the Domain Name field MUST be encoded
#          as described in Section 8 of [5]"
class DHCP6OptClientFQDN(_DHCP6OptGuessPayload):                 #RFC4704
    name = "DHCP6 Option - Client FQDN"
    fields_desc = [ ShortEnumField("optcode", 39, dhcp6opts),
                    FieldLenField("optlen", None, length_of="fqdn",
                                  adjust = lambda pkt,x: x+1),
                    BitField("res", 0, 5),
                    FlagsField("flags", 0, 3, "SON" ),
                    DomainNameField("fqdn", "",
                                    length_from = lambda pkt: pkt.optlen-1) ]

class DHCP6OptRelayAgentERO(_DHCP6OptGuessPayload):       # RFC4994
    name = "DHCP6 Option - RelayRequest Option"
    fields_desc = [ ShortEnumField("optcode", 43, dhcp6opts),
                    FieldLenField("optlen", None, length_of="reqopts", fmt="!H"),
                    _OptReqListField("reqopts", [23, 24],
                                     length_from = lambda pkt: pkt.optlen) ]

#####################################################################
###                        DHCPv6 messages                        ###
#####################################################################

# Some state parameters of the protocols that should probably be 
# useful to have in the configuration (and keep up-to-date)
DHCP6RelayAgentUnicastAddr=""
DHCP6RelayHopCount=""
DHCP6ServerUnicastAddr=""
DHCP6ClientUnicastAddr=""
DHCP6ClientIA_TA=""
DHCP6ClientIA_NA=""
DHCP6ClientIAID=""
T1="" # Voir 2462
T2="" # Voir 2462
DHCP6ServerDUID=""
DHCP6CurrentTransactionID="" # devrait etre utilise pour matcher une
# reponse et mis a jour en mode client par une valeur aleatoire pour
# laquelle on attend un retour de la part d'un serveur.
DHCP6PrefVal="" # la valeur de preference a utiliser dans
# les options preference

# Emitted by :
# - server : ADVERTISE, REPLY, RECONFIGURE, RELAY-REPL (vers relay)
# - client : SOLICIT, REQUEST, CONFIRM, RENEW, REBIND, RELEASE, DECLINE,
#            INFORMATION REQUEST
# - relay  : RELAY-FORW (toward server)

class _DHCP6GuessPayload(Packet):
    def guess_payload_class(self, payload):
        if len(payload) > 1 :
            print ord(payload[0])
            return get_cls(dhcp6opts.get(ord(payload[0]),"DHCP6OptUnknown"), conf.raw_layer)
        return conf.raw_layer

#####################################################################
## DHCPv6 messages sent between Clients and Servers (types 1 to 11)
# Comme specifie en section 15.1 de la RFC 3315, les valeurs de
# transaction id sont selectionnees de maniere aleatoire par le client
# a chaque emission et doivent matcher dans les reponses faites par
# les clients
class DHCP6(_DHCP6OptGuessPayload):
    name = "DHCPv6 Generic Message)"
    fields_desc = [ ByteEnumField("msgtype",None,dhcp6types),
                    X3BytesField("trid",0x000000) ]
    overload_fields = { UDP: {"sport": 546, "dport": 547} }

    def hashret(self):
        return struct.pack("!I", self.trid)[1:4]

#####################################################################
# Solicit Message : sect 17.1.1 RFC3315
# - sent by client
# - must include a client identifier option
# - the client may include IA options for any IAs to which it wants the
#   server to assign address
# - The client use IA_NA options to request the assignment of
#   non-temporary addresses and uses IA_TA options to request the
#   assignment of temporary addresses
# - The client should include an Option Request option to indicate the
#   options the client is interested in receiving (eventually
#   including hints)
# - The client includes a Reconfigure Accept option if is willing to
#   accept Reconfigure messages from the server.
# Le cas du send and reply est assez particulier car suivant la
# presence d'une option rapid commit dans le solicit, l'attente
# s'arrete au premier message de reponse recu ou alors apres un
# timeout. De la meme maniere, si un message Advertise arrive avec une
# valeur de preference de 255, il arrete l'attente et envoie une
# Request.
# - The client announces its intention to use DHCP authentication by
# including an Authentication option in its solicit message. The
# server selects a key for the client based on the client's DUID. The
# client and server use that key to authenticate all DHCP messages
# exchanged during the session

class DHCP6_Solicit(DHCP6):
    name = "DHCPv6 Solicit Message"
    msgtype = 1
    overload_fields = { UDP: {"sport": 546, "dport": 547} }

#####################################################################
# Advertise Message
# - sent by server
# - Includes a server identifier option
# - Includes a client identifier option
# - the client identifier option must match the client's DUID
# - transaction ID must match

class DHCP6_Advertise(DHCP6):
    name = "DHCPv6 Advertise Message"
    msgtype = 2
    overload_fields = { UDP: {"sport": 547, "dport": 546} }
    
    def answers(self, other):
        return (isinstance(other,DHCP6_Solicit) and 
                other.msgtype == 1 and
                self.trid == other.trid)

#####################################################################
# Request Message
# - sent by clients
# - includes a server identifier option
# - the content of Server Identifier option must match server's DUID
# - includes a client identifier option
# - must include an ORO Option (even with hints) p40
# - can includes a reconfigure Accept option indicating whether or
#   not the client is willing to accept Reconfigure messages from
#   the server (p40)
# - When the server receives a Request message via unicast from a
# client to which the server has not sent a unicast option, the server
# discards the Request message and responds with a Reply message
# containinig Status Code option with the value UseMulticast, a Server
# Identifier Option containing the server's DUID, the client
# Identifier option from the client message and no other option.

class DHCP6_Request(DHCP6):
    name = "DHCPv6 Request Message"
    msgtype = 3

#####################################################################
# Confirm Message
# - sent by clients
# - must include a clien identifier option
# - When the server receives a Confirm Message, the server determines
# whether the addresses in the Confirm message are appropriate for the
# link to which the client is attached. cf p50

class DHCP6_Confirm(DHCP6):
    name = "DHCPv6 Confirm Message"
    msgtype = 4
    
#####################################################################
# Renew Message
# - sent by clients
# - must include a server identifier option
# - content of server identifier option must match the server's identifier
# - must include a client identifier option
# - the clients includes any IA assigned to the interface that may
# have moved to a new link, along with the addresses associated with
# those IAs in its confirm messages
# - When the server receives a Renew message that contains an IA
# option from a client, it locates the client's binding and verifies
# that the information in the IA from the client matches the
# information for that client. If the server cannot find a client
# entry for the IA the server returns the IA containing no addresses
# with a status code option est to NoBinding in the Reply message. cf
# p51 pour le reste.

class DHCP6_Renew(DHCP6):
    name = "DHCPv6 Renew Message"
    msgtype = 5
    
#####################################################################
# Rebind Message
# - sent by clients
# - must include a client identifier option
# cf p52

class DHCP6_Rebind(DHCP6):
    name = "DHCPv6 Rebind Message"
    msgtype = 6
    
#####################################################################
# Reply Message
# - sent by servers
# - the message must include a server identifier option
# - transaction-id field must match the value of original message
# The server includes a Rapid Commit option in the Reply message to
# indicate that the reply is in response to a solicit message
# - if the client receives a reply message with a Status code option
# with the value UseMulticast, the client records the receipt of the
# message and sends subsequent messages to the server through the
# interface on which the message was received using multicast. The
# client resends the original message using multicast
# - When the client receives a NotOnLink status from the server in
# response to a Confirm message, the client performs DHCP server
# solicitation as described in section 17 and client-initiated
# configuration as descrribed in section 18 (RFC 3315)
# - when the client receives a NotOnLink status from the server in
# response to a Request, the client can either re-issue the Request
# without specifying any addresses or restart the DHCP server
# discovery process.
# - the server must include a server identifier option containing the
# server's DUID in the Reply message

class DHCP6_Reply(DHCP6):
    name = "DHCPv6 Reply Message"
    msgtype = 7
    
    def answers(self, other):
        return (isinstance(other, DHCP6_InfoRequest) and
                self.trid == other.trid)

#####################################################################
# Release Message
# - sent by clients
# - must include a server identifier option
# cf p53

class DHCP6_Release(DHCP6):
    name = "DHCPv6 Release Message"
    msgtype = 8
    
#####################################################################
# Decline Message
# - sent by clients
# - must include a client identifier option
# - Server identifier option must match server identifier
# - The addresses to be declined must be included in the IAs. Any
# addresses for the IAs the client wishes to continue to use should
# not be in added to the IAs.
# - cf p54 

class DHCP6_Decline(DHCP6):
    name = "DHCPv6 Decline Message"
    msgtype = 9
    
#####################################################################
# Reconfigure Message
# - sent by servers
# - must be unicast to the client
# - must include a server identifier option
# - must include a client identifier option that contains the client DUID
# - must contain a Reconfigure Message Option and the message type
#   must be a valid value
# - the server sets the transaction-id to 0
# - The server must use DHCP Authentication in the Reconfigure
# message. Autant dire que ca va pas etre le type de message qu'on va
# voir le plus souvent.

class DHCP6_Reconf(DHCP6):
    name = "DHCPv6 Reconfigure Message"
    msgtype = 10
    overload_fields = { UDP: { "sport": 547, "dport": 546 } }

    
#####################################################################
# Information-Request Message
# - sent by clients when needs configuration information but no
# addresses. 
# - client should include a client identifier option to identify
# itself. If it doesn't the server is not able to return client
# specific options or the server can choose to not respond to the
# message at all. The client must include a client identifier option
# if the message will be authenticated.
# - client must include an ORO of option she's interested in receiving
# (can include hints)

class DHCP6_InfoRequest(DHCP6):
    name = "DHCPv6 Information Request Message"    
    msgtype = 11 
    
    def hashret(self): 
        return struct.pack("!I", self.trid)[1:3]

#####################################################################
# sent between Relay Agents and Servers 
#
# Normalement, doit inclure une option "Relay Message Option"
# peut en inclure d'autres.
# voir section 7.1 de la 3315

# Relay-Forward Message
# - sent by relay agents to servers
# If the relay agent relays messages to the All_DHCP_Servers multicast
# address or other multicast addresses, it sets the Hop Limit field to
# 32. 

class DHCP6_RelayForward(_DHCP6GuessPayload,Packet):
    name = "DHCPv6 Relay Forward Message (Relay Agent/Server Message)"
    fields_desc = [ ByteEnumField("msgtype", 12, dhcp6types),
                    ByteField("hopcount", None),
                    IP6Field("linkaddr", "::"),
                    IP6Field("peeraddr", "::") ]
    def hashret(self): # we filter on peer address field
        return inet_pton(socket.AF_INET6, self.peeraddr)

#####################################################################
# sent between Relay Agents and Servers 
# Normalement, doit inclure une option "Relay Message Option"
# peut en inclure d'autres.
# Les valeurs des champs hop-count, link-addr et peer-addr
# sont copiees du messsage Forward associe. POur le suivi de session.
# Pour le moment, comme decrit dans le commentaire, le hashret
# se limite au contenu du champ peer address.
# Voir section 7.2 de la 3315.

# Relay-Reply Message
# - sent by servers to relay agents
# - if the solicit message was received in a Relay-Forward message,
# the server constructs a relay-reply message with the Advertise
# message in the payload of a relay-message. cf page 37/101. Envoie de
# ce message en unicast au relay-agent. utilisation de l'adresse ip
# presente en ip source du paquet recu

class DHCP6_RelayReply(DHCP6_RelayForward):
    name = "DHCPv6 Relay Reply Message (Relay Agent/Server Message)"
    msgtype = 13
    def hashret(self): # We filter on peer address field.
        return inet_pton(socket.AF_INET6, self.peeraddr)
    def answers(self, other):
        return (isinstance(other, DHCP6_RelayForward) and
                self.count == other.count and
                self.linkaddr == other.linkaddr and
                self.peeraddr == other.peeraddr )


dhcp6_cls_by_type = {  1: "DHCP6_Solicit",
                       2: "DHCP6_Advertise",
                       3: "DHCP6_Request",
                       4: "DHCP6_Confirm",
                       5: "DHCP6_Renew",
                       6: "DHCP6_Rebind",
                       7: "DHCP6_Reply",
                       8: "DHCP6_Release",
                       9: "DHCP6_Decline",
                      10: "DHCP6_Reconf",
                      11: "DHCP6_InfoRequest",
                      12: "DHCP6_RelayForward",
                      13: "DHCP6_RelayReply" }

def _dhcp6_dispatcher(x, *args, **kargs):
    cls = conf.raw_layer
    if len(x) >= 2:
        cls = get_cls(dhcp6_cls_by_type.get(ord(x[0]), "Raw"), conf.raw_layer)
    return cls(x, *args, **kargs)

bind_bottom_up(UDP, _dhcp6_dispatcher, { "dport": 547 } )
bind_bottom_up(UDP, _dhcp6_dispatcher, { "dport": 546 } )



class DHCPv6_am(AnsweringMachine):
    function_name = "dhcp6d"
    filter = "udp and port 546 and port 547" 
    send_function = staticmethod(send)
    def usage(self):
        msg = """
dhcp6d( dns="2001:500::1035", domain="localdomain, local", duid=None)
        iface=conf.iface6, advpref=255, sntpservers=None, 
        sipdomains=None, sipservers=None, 
        nisdomain=None, nisservers=None, 
        nispdomain=None, nispservers=None,
        bcmcsdomain=None, bcmcsservers=None)

   debug : When set, additional debugging information is printed. 

   duid   : some DUID class (DUID_LLT, DUID_LL or DUID_EN). If none
            is provided a DUID_LLT is constructed based on the MAC 
            address of the sending interface and launch time of dhcp6d 
            answering machine. 
  
   iface : the interface to listen/reply on if you do not want to use 
           conf.iface6.

   advpref : Value in [0,255] given to Advertise preference field.
             By default, 255 is used. Be aware that this specific
             value makes clients stops waiting for further Advertise
             messages from other servers.

   dns : list of recursive DNS servers addresses (as a string or list). 
         By default, it is set empty and the associated DHCP6OptDNSServers
         option is inactive. See RFC 3646 for details.
   domain : a list of DNS search domain (as a string or list). By default, 
         it is empty and the associated DHCP6OptDomains option is inactive.
         See RFC 3646 for details.

   sntpservers : a list of SNTP servers IPv6 addresses. By default,
         it is empty and the associated DHCP6OptSNTPServers option 
         is inactive. 

   sipdomains : a list of SIP domains. By default, it is empty and the
         associated DHCP6OptSIPDomains option is inactive. See RFC 3319
         for details.
   sipservers : a list of SIP servers IPv6 addresses. By default, it is 
         empty and the associated DHCP6OptSIPDomains option is inactive. 
         See RFC 3319 for details.

   nisdomain : a list of NIS domains. By default, it is empty and the
         associated DHCP6OptNISDomains option is inactive. See RFC 3898
         for details. See RFC 3646 for details.
   nisservers : a list of NIS servers IPv6 addresses. By default, it is 
         empty and the associated DHCP6OptNISServers option is inactive.
         See RFC 3646 for details.

   nispdomain : a list of NIS+ domains. By default, it is empty and the
         associated DHCP6OptNISPDomains option is inactive. See RFC 3898
         for details.
   nispservers : a list of NIS+ servers IPv6 addresses. By default, it is 
         empty and the associated DHCP6OptNISServers option is inactive.
         See RFC 3898 for details.

   bcmcsdomain : a list of BCMCS domains. By default, it is empty and the
         associated DHCP6OptBCMCSDomains option is inactive. See RFC 4280
         for details.
   bcmcsservers : a list of BCMCS servers IPv6 addresses. By default, it is 
         empty and the associated DHCP6OptBCMCSServers option is inactive.
         See RFC 4280 for details.

   If you have a need for others, just ask ... or provide a patch."""
        print msg

    def parse_options(self, dns="2001:500::1035", domain="localdomain, local",
                      startip="2001:db8::1", endip="2001:db8::20", duid=None,
                      sntpservers=None, sipdomains=None, sipservers=None, 
                      nisdomain=None, nisservers=None, nispdomain=None,
                      nispservers=None, bcmcsservers=None, bcmcsdomains=None,
                      iface=None, debug=0, advpref=255):
        def norm_list(val, param_name):
            if val is None:
                return None
            if type(val) is list:
                return val
            elif type(val) is str:
                l = val.split(',')
                return map(lambda x: x.strip(), l)
            else:
                print "Bad '%s' parameter provided." % param_name
                self.usage()
                return -1

        if iface is None:
            iface = conf.iface6
        
        self.debug = debug

        # Dictionary of provided DHCPv6 options, keyed by option type
        self.dhcpv6_options={}

        for o in [(dns, "dns", 23, lambda x: DHCP6OptDNSServers(dnsservers=x)), 
                  (domain, "domain", 24, lambda x: DHCP6OptDNSDomains(dnsdomains=x)), 
                  (sntpservers, "sntpservers", 31, lambda x: DHCP6OptSNTPServers(sntpservers=x)),
                  (sipservers, "sipservers", 22, lambda x: DHCP6OptSIPServers(sipservers=x)),
                  (sipdomains, "sipdomains", 21, lambda x: DHCP6OptSIPDomains(sipdomains=x)),
                  (nisservers, "nisservers", 27, lambda x: DHCP6OptNISServers(nisservers=x)),
                  (nisdomain, "nisdomain", 29, lambda x: DHCP6OptNISDomain(nisdomain=(x+[""])[0])),
                  (nispservers, "nispservers", 28, lambda x: DHCP6OptNISPServers(nispservers=x)), 
                  (nispdomain, "nispdomain", 30, lambda x: DHCP6OptNISPDomain(nispdomain=(x+[""])[0])),
                  (bcmcsservers, "bcmcsservers", 33, lambda x: DHCP6OptBCMCSServers(bcmcsservers=x)),
                  (bcmcsdomains, "bcmcsdomains", 34, lambda x: DHCP6OptBCMCSDomains(bcmcsdomains=x))]:

            opt = norm_list(o[0], o[1])
            if opt == -1: # Usage() was triggered
                return False
            elif opt is None: # We won't return that option
                pass
            else:
                self.dhcpv6_options[o[2]] = o[3](opt)

        if self.debug:
            print "\n[+] List of active DHCPv6 options:"
            opts = self.dhcpv6_options.keys()
            opts.sort()
            for i in opts:
                print "    %d: %s" % (i, repr(self.dhcpv6_options[i]))

        # Preference value used in Advertise. 
        self.advpref = advpref

        # IP Pool
        self.startip = startip
        self.endip   = endip
        # XXX TODO Check IPs are in same subnet

        ####
        # The interface we are listening/replying on
        self.iface = iface

        ####        
        # Generate a server DUID
        if duid is not None:
            self.duid = duid
        else:
            # Timeval
            from time import gmtime, strftime, mktime
            epoch = (2000, 1, 1, 0, 0, 0, 5, 1, 0)
            delta = mktime(epoch) - mktime(gmtime(0))
            timeval = time.time() - delta

            # Mac Address
            rawmac = get_if_raw_hwaddr(iface)[1]
            mac = ":".join(map(lambda x: "%.02x" % ord(x), list(rawmac)))

            self.duid = DUID_LLT(timeval = timeval, lladdr = mac)
            
        if self.debug:
            print "\n[+] Our server DUID:" 
            self.duid.show(label_lvl=" "*4)

        ####
        # Find the source address we will use
        l = filter(lambda x: x[2] == iface and in6_islladdr(x[0]), 
                   in6_getifaddr())
        if not l:
            warning("Unable to get a Link-Local address")
            return 
        
        self.src_addr = l[0][0]

        ####
        # Our leases
        self.leases = {}
        

        if self.debug:
            print "\n[+] Starting DHCPv6 service on %s:" % self.iface 

    def is_request(self, p):
        if not IPv6 in p:
            return False

        src = p[IPv6].src
        dst = p[IPv6].dst

        p = p[IPv6].payload 
        if not isinstance(p, UDP) or p.sport != 546 or p.dport != 547 :
            return False

        p = p.payload
        if not isinstance(p, DHCP6):
            return False

        # Message we considered client messages :
        # Solicit (1), Request (3), Confirm (4), Renew (5), Rebind (6)
        # Decline (9), Release (8), Information-request (11),
        if not (p.msgtype in [1, 3, 4, 5, 6, 8, 9, 11]):
            return False

        # Message validation following section 15 of RFC 3315

        if ((p.msgtype == 1) or # Solicit 
            (p.msgtype == 6) or # Rebind
            (p.msgtype == 4)):  # Confirm
            if ((not DHCP6OptClientId in p) or
                DHCP6OptServerId in p):
                return False

            if (p.msgtype == 6 or # Rebind
                p.msgtype == 4):  # Confirm   
                # XXX We do not reply to Confirm or Rebind as we 
                # XXX do not support address assignment            
                return False

        elif (p.msgtype == 3 or # Request
              p.msgtype == 5 or # Renew
              p.msgtype == 8):  # Release
        
            # Both options must be present
            if ((not DHCP6OptServerId in p) or
                (not DHCP6OptClientId in p)):
                return False
            # provided server DUID must match ours
            duid = p[DHCP6OptServerId].duid
            if (type(duid) != type(self.duid)):
                return False
            if str(duid) != str(self.duid):
                return False

            if (p.msgtype == 5 or # Renew
                p.msgtype == 8):  # Release
                # XXX We do not reply to Renew or Release as we 
                # XXX do not support address assignment            
                return False

        elif p.msgtype == 9: # Decline
            # XXX We should check if we are tracking that client
            if not self.debug:
                return False

            bo = Color.bold
            g = Color.green + bo
            b = Color.blue + bo
            n = Color.normal
            r = Color.red

            vendor  = in6_addrtovendor(src)
            if (vendor and vendor != "UNKNOWN"):
                vendor = " [" + b + vendor + n + "]"
            else:
                vendor = ""
            src  = bo + src + n

            it = p
            addrs = []
            while it:
                l = []
                if isinstance(it, DHCP6OptIA_NA):
                    l = it.ianaopts
                elif isinstance(it, DHCP6OptIA_TA):
                    l = it.iataopts

                opsaddr = filter(lambda x: isinstance(x, DHCP6OptIAAddress),l)
                a=map(lambda x: x.addr,  opsaddr)
                addrs += a
                it = it.payload
                    
            addrs = map(lambda x: bo + x + n, addrs)
            if debug:
                msg = r + "[DEBUG]" + n + " Received " + g + "Decline" + n 
                msg += " from " + bo + src + vendor + " for "
                msg += ", ".join(addrs)+ n
                print msg

            # See sect 18.1.7

            # Sent by a client to warn us she has determined
            # one or more addresses assigned to her is already
            # used on the link.
            # We should simply log that fact. No messaged should
            # be sent in return.

            # - Message must include a Server identifier option
            # - the content of the Server identifier option must 
            #   match the server's identifier
            # - the message must include a Client Identifier option
            return False

        elif p.msgtype == 11: # Information-Request
            if DHCP6OptServerId in p:
                duid = p[DHCP6OptServerId].duid
                if (type(duid) != type(self.duid)):
                    return False
                if str(duid) != str(self.duid):
                    return False
            if ((DHCP6OptIA_NA in p) or 
                (DHCP6OptIA_TA in p) or
                (DHCP6OptIA_PD in p)):
                    return False
        else:
            return False

        return True

    def print_reply(self, req, reply):
        def norm(s):
            if s.startswith("DHCPv6 "):
                s = s[7:]
            if s.endswith(" Message"):
                s = s[:-8]
            return s
        
        if reply is None:
            return

        bo = Color.bold
        g = Color.green + bo
        b = Color.blue + bo
        n = Color.normal
        reqtype = g + norm(req.getlayer(UDP).payload.name) + n
        reqsrc  = req.getlayer(IPv6).src
        vendor  = in6_addrtovendor(reqsrc)
        if (vendor and vendor != "UNKNOWN"):
            vendor = " [" + b + vendor + n + "]"
        else:
            vendor = ""
        reqsrc  = bo + reqsrc + n
        reptype = g + norm(reply.getlayer(UDP).payload.name) + n

        print "Sent %s answering to %s from %s%s" % (reptype, reqtype, reqsrc, vendor)

    def make_reply(self, req):
        req_mac_src = req.src
        req_mac_dst = req.dst

        p = req[IPv6]
        req_src = p.src
        req_dst = p.dst

        p = p.payload.payload

        msgtype = p.msgtype
        trid = p.trid

        if msgtype == 1: # SOLICIT (See Sect 17.1 and 17.2 of RFC 3315)
            
            # XXX We don't support address or prefix assignment
            # XXX We also do not support relay function           --arno

            client_duid = p[DHCP6OptClientId].duid
            resp  = IPv6(src=self.src_addr, dst=req_src)
            resp /= UDP(sport=547, dport=546)
            
            if p.haslayer(DHCP6OptRapidCommit):
                # construct a Reply packet 
                resp /= DHCP6_Reply(trid=trid)
                resp /= DHCP6OptRapidCommit() # See 17.1.2
                resp /= DHCP6OptServerId(duid = self.duid)
                resp /= DHCP6OptClientId(duid = client_duid)
                
            else: # No Rapid Commit in the packet. Reply with an Advertise                
                
                if (p.haslayer(DHCP6OptIA_NA) or
                    p.haslayer(DHCP6OptIA_TA)):
                    # XXX We don't assign addresses at the moment
                    msg = "Scapy6 dhcp6d does not support address assignment"
                    resp /= DHCP6_Advertise(trid = trid)
                    resp /= DHCP6OptStatusCode(statuscode=2, statusmsg=msg)
                    resp /= DHCP6OptServerId(duid = self.duid)
                    resp /= DHCP6OptClientId(duid = client_duid)                  

                elif p.haslayer(DHCP6OptIA_PD):
                    # XXX We don't assign prefixes at the moment
                    msg = "Scapy6 dhcp6d does not support prefix assignment"
                    resp /= DHCP6_Advertise(trid = trid)
                    resp /= DHCP6OptStatusCode(statuscode=6, statusmsg=msg)
                    resp /= DHCP6OptServerId(duid = self.duid)
                    resp /= DHCP6OptClientId(duid = client_duid)                  

                else: # Usual case, no request for prefixes or addresse
                    resp /= DHCP6_Advertise(trid = trid)
                    resp /= DHCP6OptPref(prefval = self.advpref)
                    resp /= DHCP6OptServerId(duid = self.duid)
                    resp /= DHCP6OptClientId(duid = client_duid)
                    resp /= DHCP6OptReconfAccept()
                    
                    # See which options should be included
                    reqopts = []
                    if p.haslayer(DHCP6OptOptReq): # add only asked ones
                        reqopts = p[DHCP6OptOptReq].reqopts
                        for o in self.dhcpv6_options.keys():
                            if o in reqopts:
                                resp /= self.dhcpv6_options[o]
                    else: # advertise everything we have available
                        for o in self.dhcpv6_options.keys():
                            resp /= self.dhcpv6_options[o]                    

            return resp

        elif msgtype == 3: #REQUEST (INFO-REQUEST is further below)
            client_duid = p[DHCP6OptClientId].duid
            resp  = IPv6(src=self.src_addr, dst=req_src)
            resp /= UDP(sport=547, dport=546)
            resp /= DHCP6_Solicit(trid=trid)
            resp /= DHCP6OptServerId(duid = self.duid)
            resp /= DHCP6OptClientId(duid = client_duid)

            # See which options should be included
            reqopts = []
            if p.haslayer(DHCP6OptOptReq): # add only asked ones
                reqopts = p[DHCP6OptOptReq].reqopts
                for o in self.dhcpv6_options.keys():
                    if o in reqopts:
                        resp /= self.dhcpv6_options[o]
            else: 
                # advertise everything we have available.
                # Should not happen has clients MUST include 
                # and ORO in requests (sec 18.1.1)   -- arno
                for o in self.dhcpv6_options.keys():
                    resp /= self.dhcpv6_options[o]          

            return resp            
        
        elif msgtype == 4: # CONFIRM
            # see Sect 18.1.2
            
            # Client want to check if addresses it was assigned
            # are still appropriate

            # Server must discard any Confirm messages that
            # do not include a Client Identifier option OR
            # THAT DO INCLUDE a Server Identifier Option

            # XXX we must discard the SOLICIT if it is received with
            #     a unicast destination address

            pass

        elif msgtype == 5: # RENEW
            # see Sect 18.1.3
            
            # Clients want to extend lifetime of assigned addresses
            # and update configuration parameters. This message is sent
            # specifically to the server that provided her the info

            # - Received message must include a Server Identifier
            #   option.
            # - the content of server identifier option must match
            #   the server's identifier.
            # - the message must include a Client identifier option

            pass
        
        elif msgtype == 6: # REBIND
            # see Sect 18.1.4
            
            # Same purpose as the Renew message but sent to any
            # available server after he received no response
            # to its previous Renew message.

            
            # - Message must include a Client Identifier Option
            # - Message can't include a Server identifier option

            # XXX we must discard the SOLICIT if it is received with
            #     a unicast destination address

            pass

        elif msgtype == 8: # RELEASE
            # See section 18.1.6

            # Message is sent to the server to indicate that 
            # she will no longer use the addresses that was assigned
            # We should parse the message and verify our dictionary
            # to log that fact.


            # - The message must include a server identifier option
            # - The content of the Server Identifier option must
            #   match the server's identifier
            # - the message must include a Client Identifier option

            pass

        elif msgtype == 9: # DECLINE
            # See section 18.1.7            
            pass

        elif msgtype == 11: # INFO-REQUEST
            client_duid = None
            if not p.haslayer(DHCP6OptClientId):
                if self.debug:
                    warning("Received Info Request message without Client Id option")
            else:
                client_duid = p[DHCP6OptClientId].duid

            resp  = IPv6(src=self.src_addr, dst=req_src)
            resp /= UDP(sport=547, dport=546)
            resp /= DHCP6_Reply(trid=trid)
            resp /= DHCP6OptServerId(duid = self.duid)

            if client_duid:
                resp /= DHCP6OptClientId(duid = client_duid)
                
            # Stack requested options if available
            reqopts = []
            if p.haslayer(DHCP6OptOptReq):
                reqopts = p[DHCP6OptOptReq].reqopts
            for o in self.dhcpv6_options.keys():
                resp /= self.dhcpv6_options[o]

            return resp

        else:
            # what else ?
            pass

        # - We won't support reemission
        # - We won't support relay role, nor relay forwarded messages
        #   at the beginning
